const express = require('express');
const multer = require('multer');
const fs = require('fs'); // 保留原始fs模块用于流操作
const fsPromises = require('fs').promises; // 使用promises版本处理异步文件操作
const fse = require('fs-extra');
const path = require('path');
const dayjs = require('dayjs');
const { exec } = require('child_process');
const { promisify } = require('util');
const unzipper = require('unzipper');
const archiver = require('archiver');
const ci = require('miniprogram-ci');
const crypto = require('crypto');
const portfinder = require('portfinder');

const app = express();
const port = 8000;
const COMPILED_PATH_CONST = path.dirname(require.main.filename);
const execPromise = promisify(exec);

// 配置上传中间件
const storage = multer.diskStorage({
    destination: async (req, file, cb) => {
        try {
            const params = new URLSearchParams(req.originalUrl);
            const timestamp = params.get('timestamp') || Date.now().toString();
            const uploadType = req.url.includes("weapp?") ? 'weapp' : timestamp;
            const uploadPath = path.join(COMPILED_PATH_CONST, 'compile', uploadType);

            await fsPromises.mkdir(uploadPath, { recursive: true });
            req.compilePath = uploadPath; // 将路径存储在请求对象中
            cb(null, uploadPath);
        } catch (err) {
            console.error('创建上传目录失败:', err);
            cb(err, null);
        }
    },
    filename: (req, file, cb) => {
        const filename = req.url.includes("weapp?") ? 'weapp.zip' : 'app.zip';
        cb(null, filename);
    }
});

const upload = multer({
    storage,
    limits: { fileSize: 100 * 1024 * 1024 } // 限制文件大小为100MB
});

// 工具类
class FileUtils {
    static async unzipFile(zipPath, extractPath) {
        try {
            await fse.ensureDir(extractPath);
            await fse.createReadStream(zipPath)
                .pipe(unzipper.Extract({ path: extractPath }))
                .promise();
            console.log(`解压成功: ${zipPath} 到 ${extractPath}`);
            return true;
        } catch (err) {
            console.error('解压失败:', err);
            throw new Error(`解压失败: ${err.message}`);
        }
    }

    static async deleteDirectory(dirPath) {
        if (await this.pathExists(dirPath)) {
            await fse.remove(dirPath);
            console.log(`目录已删除: ${dirPath}`);
        }
    }

    static async pathExists(path) {
        try {
            await fsPromises.access(path);
            return true;
        } catch {
            return false;
        }
    }

    static async readFile(filePath) {
        try {
            return await fsPromises.readFile(filePath, 'utf8');
        } catch (err) {
            console.error('读取文件失败:', err);
            return '';
        }
    }
}

// 编译服务类
class CompileService {
    constructor(compilePath) {
        this.compilePath = compilePath;
        this.logPath = path.join(compilePath, 'log.txt');
    }

    async log(action, code = 1, msg = '', percent = '') {
        const logEntry = {
            time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
            action,
            code,
            msg,
            percent
        };

        try {
            await fsPromises.appendFile(this.logPath, JSON.stringify(logEntry) + '\n', 'utf8');
        } catch (err) {
            console.error('记录日志失败:', err);
        }
    }

    async execute(command) {
        try {
            await this.log(command, 1, '开始执行', '');
            const { stdout, stderr } = await execPromise(command);

            if (stderr) {
                console.warn('命令警告:', stderr);
                await this.log(command, 1, stderr, '');
            }

            console.log('命令输出:', stdout);
            await this.log(command, 1, stdout, '');
            return true;
        } catch (err) {
            console.error('命令执行失败:', err.stderr || err.message);
            await this.log(command, 0, err.stderr || err.message, '');
            return false;
        }
    }

    async build() {
        try {
            const zipPath = path.join(this.compilePath, 'app.zip');
            await this.log('开始解压文件', 1, '', '10');
            await FileUtils.unzipFile(zipPath, this.compilePath);

            await this.log('开始编译项目', 1, '', '20');
            await this.compile();

            await this.log('编译完成', 1, 'success', '100');
            return true;
        } catch (err) {
            console.error('构建过程失败:', err);
            await this.log('构建失败', 0, err.message, '100');
            return false;
        }
    }

    async compile() {
        const compileTasks = [];

        // 添加各项目类型的编译任务
        if (await FileUtils.pathExists(path.join(this.compilePath, 'admin'))) {
            compileTasks.push(this.compileAdmin());
        }

        if (await FileUtils.pathExists(path.join(this.compilePath, 'uni-app'))) {
            compileTasks.push(this.compileUniApp());
        }

        if (await FileUtils.pathExists(path.join(this.compilePath, 'web'))) {
            compileTasks.push(this.compileWeb());
        }

        // 并行执行编译任务
        await Promise.all(compileTasks);

        // 合并编译结果
        await this.mergeCompiledResults();

        // 压缩最终结果
        await this.zipCompiledResults();

        // 清理临时文件
        await this.cleanupTempFiles();
    }

    async compileAdmin() {
        await this.log('编译admin项目 (npm install)...', 1, '', '30');
        await this.execute(`cd ${path.join(this.compilePath, 'admin')} && npm install`);

        await this.log('编译admin项目...', 1, '', '40');
        await this.execute(`cd ${path.join(this.compilePath, 'admin')} && npm run build`);
    }

    async compileUniApp() {
        await this.log('编译uni-app项目 (npm install)...', 1, '', '50');
        await this.execute(`cd ${path.join(this.compilePath, 'uni-app')} && npm install`);

        await this.log('编译uni-app项目...', 1, '', '60');
        await this.execute(`cd ${path.join(this.compilePath, 'uni-app')} && npm run build:h5`);
    }

    async compileWeb() {
        await this.log('编译web项目...', 1, '', '70');
        await this.execute(`cd ${path.join(this.compilePath, 'web')} && npm install && npm run generate`);
    }

    async mergeCompiledResults() {
        const publicPath = path.join(this.compilePath, 'app-compile', 'public');
        await fsPromises.mkdir(publicPath, { recursive: true });

        // 合并admin项目
        if (await FileUtils.pathExists(path.join(this.compilePath, 'admin', 'dist'))) {
            await this.log('复制admin编译结果...', 1, '', '80');
            await fse.copy(
                path.join(this.compilePath, 'admin', 'dist'),
                path.join(publicPath, 'admin')
            );
        }

        // 合并uni-app项目
        if (await FileUtils.pathExists(path.join(this.compilePath, 'uni-app', 'dist', 'build', 'h5'))) {
            await this.log('复制uni-app编译结果...', 1, '', '85');
            await fse.copy(
                path.join(this.compilePath, 'uni-app', 'dist', 'build', 'h5'),
                path.join(publicPath, 'wap')
            );
        }

        // 合并web项目
        if (await FileUtils.pathExists(path.join(this.compilePath, 'web', '.output', 'public'))) {
            await this.log('复制web编译结果...', 1, '', '90');
            await fse.copy(
                path.join(this.compilePath, 'web', '.output', 'public'),
                path.join(publicPath, 'web')
            );
        }

        // 复制vendor目录
        if (await FileUtils.pathExists(path.join(this.compilePath, 'vendor'))) {
            await this.log('复制vendor目录...', 1, '', '95');
            await fse.copy(
                path.join(this.compilePath, 'vendor'),
                path.join(this.compilePath, 'app-compile', 'vendor')
            );
        }
    }

    async zipCompiledResults() {
        return new Promise(async (resolve, reject) => {
            try {
                await this.log('开始压缩编译结果...', 1, '', '98');

                const outputPath = path.join(this.compilePath, 'app-compile.zip');
                // 使用原始fs模块创建写入流
                const output = fs.createWriteStream(outputPath);
                const archive = archiver('zip', { zlib: { level: 9 } });

                output.on('close', async () => {
                    console.log('压缩完成');
                    await this.log('压缩完成', 1, '', '99');
                    resolve();
                });

                archive.on('error', async (err) => {
                    console.error('压缩失败:', err);
                    await this.log('压缩失败', 0, err.message, '99');
                    reject(err);
                });

                archive.pipe(output);
                archive.directory(path.join(this.compilePath, 'app-compile'), false);
                archive.finalize();
            } catch (err) {
                console.error('压缩过程失败:', err);
                await this.log('压缩过程失败', 0, err.message, '99');
                reject(err);
            }
        });
    }

    async cleanupTempFiles() {
        await this.log('清理临时文件...', 1, '', '99');

        const directoriesToDelete = [
            path.join(this.compilePath, 'admin'),
            path.join(this.compilePath, 'uni-app'),
            path.join(this.compilePath, 'web'),
            path.join(this.compilePath, 'app-compile')
        ];

        for (const dir of directoriesToDelete) {
            await FileUtils.deleteDirectory(dir);
        }

        await this.log('临时文件清理完成', 1, '', '100');
    }
}

// 小程序服务类
class MiniProgramService {
    constructor(compilePath) {
        this.compilePath = compilePath;
        this.logPath = path.join(compilePath, 'log.txt');
        this.logger = {
            log: async (action, code, msg, percent) => {
                const logEntry = {
                    time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
                    action,
                    code,
                    msg,
                    percent
                };

                try {
                    await fsPromises.appendFile(this.logPath, JSON.stringify(logEntry) + '\n', 'utf8');
                } catch (err) {
                    console.error('记录日志失败:', err);
                }
            }
        };
    }

    async execute(command) {
        try {
            await this.logger.log(command, 1, '开始执行', '');
            const { stdout, stderr } = await execPromise(command);

            if (stderr) {
                console.warn('命令警告:', stderr);
                await this.logger.log(command, 1, stderr, '');
            }

            console.log('命令输出:', stdout);
            await this.logger.log(command, 1, stdout, '');
            return true;
        } catch (err) {
            console.error('命令执行失败:', err.stderr || err.message);
            await this.logger.log(command, 0, err.stderr || err.message, '');
            return false;
        }
    }

    async compileAndUpload(appId, version, privateKeyPath) {
        try {
            await this.logger.log('开始小程序编译...', 1, '', '30');

            // 编译uni-app为微信小程序
            const uniAppPath = path.join(this.compilePath, 'uni-app');
            if (!await FileUtils.pathExists(uniAppPath)) {
                throw new Error('未找到uni-app项目');
            }

            await this.logger.log('安装依赖...', 1, '', '40');
            if (!await this.execute(`cd ${uniAppPath} && npm install`)) {
                throw new Error('安装依赖失败');
            }

            await this.logger.log('编译为微信小程序...', 1, '', '50');
            if (!await this.execute(`cd ${uniAppPath} && npm run build:mp-weixin`)) {
                throw new Error('编译为微信小程序失败');
            }

            const miniProgramPath = path.join(uniAppPath, 'dist', 'build', 'mp-weixin');

            // 上传到小程序平台
            await this.logger.log('准备上传到小程序平台...', 1, '', '60');

            // 使用miniprogram-ci上传
            const project = new ci.Project({
                appid: appId,
                type: 'miniProgram',
                projectPath: miniProgramPath,
                privateKeyPath,
                ignores: ['node_modules/**/*']
            });

            await this.logger.log('开始上传...', 1, '', '70');
            const uploadResult = await ci.upload({
                project,
                version,
                desc: '自动上传',
                setting: {
                    es6: true,
                    es7: true,
                    minify: false
                },
                onProgressUpdate: console.log
            });

            console.log('上传成功:', uploadResult);
            await this.logger.log('上传成功', 1, JSON.stringify(uploadResult), '80');

            // 生成预览图
            await this.logger.log('生成预览图...', 1, '', '90');
            const previewResult = await ci.preview({
                project,
                desc: '自动生成预览',
                setting: {
                    es6: true,
                    es7: true,
                    minify: false
                },
                qrcodeFormat: 'image',
                qrcodeOutputDest: path.join(this.compilePath, 'preview.jpg'),
                onProgressUpdate: console.log
            });

            console.log('预览图生成成功:', previewResult);
            await this.logger.log('预览图生成成功', 1, JSON.stringify(previewResult), '100');

            return true;
        } catch (err) {
            console.error('小程序编译和上传失败:', err);
            await this.logger.log('小程序编译和上传失败', 0, err.message, '100');
            return false;
        }
    }
}

// 认证中间件
const authenticate = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    // TODO: 实现实际的认证逻辑
    // 这里使用简单的示例，实际应使用JWT或其他认证方式
    if (!token || token !== process.env.AUTH_TOKEN) {
      //  return res.status(401).json({ code: 0, msg: '未授权', data: [] });
    }

    next();
};

// API路由
app.post('/cloud/build', authenticate, upload.single('file'), async (req, res) => {
    try {
        const compilePath = req.compilePath;
        const compileService = new CompileService(compilePath);

        // 异步执行构建，立即返回响应
        setTimeout(() => {
            compileService.build()
                .catch(err => console.error('构建过程出错:', err));
        }, 100);

        res.status(200).json({ code: 1, msg: '开始云构建', data: ['文件上传成功'] });
    } catch (err) {
        console.error('处理构建请求失败:', err);
        res.status(500).json({ code: 0, msg: '云构建失败', data: [err.message] });
    }
});

app.post('/cloud/weapp', authenticate, upload.single('file'), async (req, res) => {
    try {
        const { appid, version } = req.query;
        const compilePath = req.compilePath;

        // 验证必要参数
        if (!appid || !version) {
            return res.status(400).json({ code: 0, msg: '缺少必要参数', data: [] });
        }

        // 解压文件
        const zipPath = path.join(compilePath, 'weapp.zip');
        await FileUtils.unzipFile(zipPath, compilePath);

        const privateKeyPath = path.join(compilePath, 'private.key');

        // 异步执行小程序编译和上传
        setTimeout(() => {
            const miniProgramService = new MiniProgramService(compilePath);
            miniProgramService.compileAndUpload(appid, version, privateKeyPath)
                .catch(err => console.error('小程序处理出错:', err));
        }, 100);

        res.status(200).json({ code: 1, msg: '开始云编译小程序', data: [] });
    } catch (err) {
        console.error('处理小程序请求失败:', err);
        res.status(500).json({ code: 0, msg: '云编译小程序失败', data: [err.message] });
    }
});

app.get('/cloud/get_weapp_preview', authenticate, async (req, res) => {
    try {
        const previewPath = path.join(
            COMPILED_PATH_CONST,
            'compile',
            'weapp',
            'preview.jpg'
        );

        if (await FileUtils.pathExists(previewPath)) {
            res.sendFile(previewPath);
        } else {
            res.status(404).json({ code: 0, msg: '无微信小程序预览图片', data: [] });
        }
    } catch (err) {
        console.error('获取预览图失败:', err);
        res.status(500).json({ code: 0, msg: '获取预览图失败', data: [err.message] });
    }
});

app.get('/cloud/get_weapp_logs', authenticate, async (req, res) => {
    try {
        const logPath = path.join(
            COMPILED_PATH_CONST,
            'compile',
            'weapp',
            'log.txt'
        );

        const logContent = await FileUtils.readFile(logPath);
        const logs = logContent.split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));

        res.status(200).json({ code: 1, msg: '获取日志成功', data: [logs] });
    } catch (err) {
        console.error('获取日志失败:', err);
        res.status(500).json({ code: 0, msg: '获取日志失败', data: [err.message] });
    }
});

app.get('/cloud/get_build_logs', authenticate, async (req, res) => {
    try {
        const { timestamp } = req.query;
        if (!timestamp) {
            return res.status(400).json({ code: 0, msg: '缺少时间戳参数', data: [] });
        }

        const logPath = path.join(
            COMPILED_PATH_CONST,
            'compile',
            timestamp,
            'log.txt'
        );

        if (!await FileUtils.pathExists(logPath)) {
            return res.status(404).json({ code: 0, msg: '日志文件不存在', data: [] });
        }

        const logContent = await FileUtils.readFile(logPath);
        const logs = logContent.split('\n')
            .filter(line => line.trim())
            .map(line => JSON.parse(line));

        res.status(200).json({ code: 1, msg: '获取日志成功', data: [logs] });
    } catch (err) {
        console.error('获取日志失败:', err);
        res.status(500).json({ code: 0, msg: '获取日志失败', data: [err.message] });
    }
});

app.get('/cloud/build_download', authenticate, async (req, res) => {
    try {
        const { timestamp, version } = req.query;
        if (!timestamp) {
            return res.status(400).json({ code: 0, msg: '缺少时间戳参数', data: [] });
        }

        let downloadPath;
        if (version) {
            downloadPath = path.join(
                COMPILED_PATH_CONST,
                'compile',
                timestamp,
                `${version}.zip`
            );
        } else {
            downloadPath = path.join(
                COMPILED_PATH_CONST,
                'compile',
                timestamp,
                'app-compile.zip'
            );
        }

        if (await FileUtils.pathExists(downloadPath)) {
            fs.stat(downloadPath, (err, stats) => {
                if (err) {
                    console.error(err);
                    res.status(500).send('File not found');
                    return;
                }

                const fileSize = stats.size;
                const range = req.headers.range;

                if (!range) {
                    // 不支持 Range 的客户端
                    res.status(200).header({
                        'Content-Type': 'application/octet-stream',
                        'Content-Length': fileSize,
                        'Content-Disposition': 'attachment'
                    });
                    fs.createReadStream(downloadPath).pipe(res);
                } else {
                    // 处理 Range 请求
                    const parts = range.replace(/bytes=/, '').split('-');
                    const start = parseInt(parts[0], 10);
                    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;

                    if (req.method === 'HEAD') {
                        res.header('Content-Type', 'application/octet-stream');
                        res.header('Content-Length', fileSize);
                        res.header('Content-range', `bytes ${start}-${end}/${fileSize}`)
                        return res.status(200).end();
                    }

                    if (start > fileSize) {
                        res.status(416).send('Requested range not satisfiable');
                        return;
                    }

                    const chunkSize = end - start;

                    res.status(206).header({
                        'Content-Type': 'application/octet-stream',
                        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                        'Content-Length': chunkSize,
                        'Content-Disposition': 'attachment'
                    });

                    const stream = fs.createReadStream(downloadPath, { start, end });
                    stream.pipe(res);
                }
            });
        } else {
            res.status(404).json({ code: 0, msg: '下载文件不存在', data: [] });
        }
    } catch (err) {
        console.error('处理下载请求失败:', err);
        res.status(500).json({ code: 0, msg: '下载失败', data: [err.message] });
    }
});

app.get('/', (req, res) => {
    res.send('欢迎使用NiuCloud编译服务!');
});

// 错误处理中间件
app.use((err, req, res, next) => {
    console.error('未处理的错误:', err);
    res.status(500).json({ code: 0, msg: '服务器内部错误', data: [err.message] });
});

portfinder.getPortPromise({ port }).then((availablePort) => {
    if (availablePort !== port) {
        console.error('\x1b[31m', `${port}端口已被其他进程占用服务启动失败\x1b[0m`);
    } else {
        // 启动服务器
        app.listen(port, () => {
            console.log(`NiuCloud编译服务正在运行: http://localhost:${port}`);
        });
    }
})
.catch(err => {
    console.error('Failed to find an available port:', err);
});